#include "precompiled.h"
#include "optimizefull.h"
using namespace weo::engine::optimization;
using namespace weo::engine::character;
using namespace weo::engine::item;
using namespace std;

Individual & Individual::operator =(const Individual &indiv)
{
    if(this != &indiv)
    {
        Fitness = indiv.Fitness;
        Generation = indiv.Generation;
        for(int i = 0;i<weo::engine::Slotcount;i++)
        {
            Items[i] = indiv.Items[i];
            Enchants[i] = indiv.Enchants[i];
            for(int k = 0;k<3;k++)
            {		
                Gems[i*3+k] = indiv.Gems[i*3+k];
            }

        }
        updateHash();
    }
    return *this;
}
void Individual::updateHash()
{
    Hash = 0;
    for(unsigned int i = 0;i<weo::engine::Slotcount;i++)
    {
        size_t hashval = 0;
        if(Items[i] != NULL)  hashval = Items[i]->Hash();
        boost::hash_combine(Hash, hashval);
        for(unsigned int j = 0;j<3;j++)
        {
            hashval = 0;
            if(Gems[i*3+j] != NULL) hashval = Gems[i*3+j]->Hash();
            boost::hash_combine(Hash, hashval);
        }
        hashval = 0;
        if(Enchants[i] != NULL) hashval = Enchants[i]->Hash();
        boost::hash_combine(Hash, hashval);
    }
}
OptimizeFull::OptimizeFull() : Optimization()
{
    m_MaxTV = 0;
    m_Individuals = NULL;
}
OptimizeFull::OptimizeFull(const vector<vector<const Item*>>& value) : Optimization()
{
    m_Items = filterOptimizeItems(value);
    m_MaxTV = 0;
    m_Gemindex = m_Items.size()-1;
    m_Individuals = NULL;
}
void OptimizeFull::evaluateTF(Individual& indiv)
{
    m_Character->evaluateEquip(indiv.Items,indiv.Gems,indiv.Enchants);
    float fitness = m_Character->evaluateTF();
    indiv.Fitness = fitness;
}
bool OptimizeFull::Crossover(Individual& child, const Individual& p1, const Individual& p2)
{
    unsigned int xpoint = (weo::engine::Slotcount / 4) + (std::rand() % (weo::engine::Slotcount / 2));
    child.Hash = 0;
    child.Fitness = 0;
    for(unsigned int i = 0;i<weo::engine::Slotcount;i++)
    {
        if(i < xpoint)
        {
            child.Items[i] = p1.Items[i];
            child.Enchants[i] = p1.Enchants[i];
            for(int k = 0;k<3;k++)
            {
                child.Gems[i*3+k] = p1.Gems[i*3+k];
            }
        }
        else
        {
            child.Items[i] = p2.Items[i];
            child.Enchants[i] = p2.Enchants[i];
            for(int k = 0;k<3;k++)
            {
                child.Gems[i*3+k] = p2.Gems[i*3+k];
            }
        }
    }

    child.updateHash();
    if(Valid(child))
    {
        return true;
    }
    return false;
}
inline bool OptimizeFull::Valid(Individual& indiv)
{
    if(indiv.Hash == 0) indiv.updateHash();
    size_t hashval = indiv.Hash;
    bool retval = Optimization::Valid(indiv.Items,indiv.Gems,indiv.Enchants);
    if(!retval)
    {
        m_Invalidcounter++;
        return false;
    }
    if(m_HashSet.count(hashval) == 0) // not found
    {
        m_HashSet.insert(hashval);
        return true;
    }
    if(m_StartLoop.elapsed() > 10) m_Abort = true;
    m_Invalidcounter++;
    return false;
}
inline bool cmpindiv(const Individual& a, const Individual& b)
{
    return a.Fitness < b.Fitness;
}
void OptimizeFull::Tournamentselection()
{
    int size = 4;
    int rounds = m_MaxPopulation/size;
    google::sparse_hash_set<size_t> m_UsedIndiv;
    for(int i = 0;i<rounds;i++)
    {
        int counter = 0;
        int bestindex = -1;
        float bestvalue = 0;
        while(counter < size)
        {
            int index = rand() % m_MaxPopulation;
            if(m_UsedIndiv.count(m_Individuals[index].Hash) == 0)
            {
                if(m_Individuals[index].Fitness > bestvalue)
                {
                    bestvalue = m_Individuals[index].Fitness;
                    if(bestindex != -1)
                    {
                        m_Individuals[bestindex].Fitness = 0;
                    }
                    bestindex = index;
                    m_UsedIndiv.insert(m_Individuals[index].Hash);
                }
                else
                {
                    m_Individuals[index].Fitness = 0;
                }
                counter++;
            }
        }
    }
    std::sort(m_Individuals,m_Individuals+m_MaxPopulation,cmpindiv);
}
bool OptimizeFull::Mutate(Individual& indiv)
{
    Individual wcopy = indiv;
    bool working = true;
    while(working)
    {
        int slot = std::rand() % weo::engine::Slotcount;
        if(m_Locked[slot]) continue;;
        populateSlot(wcopy,slot);
        if(!Valid(wcopy))
        {
            wcopy = indiv;
        }
        else working = false;
    }
    indiv = wcopy;
    indiv.Fitness = 0;
    return true;
}
void OptimizeFull::populateSlot(Individual& indiv, int slot)
{
    if(m_Items[slot].empty()) return;
    indiv.Hash = 0;
    Optimization::populateSlot(indiv.Items[slot],indiv.Gems[slot*3],indiv.Gems[slot*3+1],indiv.Gems[slot*3+2],indiv.Enchants[slot],slot);
}
void OptimizeFull::Optimize()
{
    //OptimizeThreadRunner();
    boost::thread thrd(boost::bind(&OptimizeFull::OptimizeThreadRunner,this));
}
void OptimizeFull::OptimizeThreadRunner()
{
    GC_stack_base gcstruck;
    GC_get_stack_base(&gcstruck);
    GC_register_my_thread(&gcstruck);
    std::srand(std::clock());
    unsigned int counter = 0;
    unsigned int childcount = 0;
    unsigned int bestgen = 0;
    unsigned int currentgen = 0;
    unsigned int  mutatecount = 0;
    float besttv = 0;
    m_Abort = false;
    m_Runtime.restart();
    m_StartLoop.restart();
    m_Invalidcounter = 0;
    m_Individuals = new Individual[m_MaxPopulation];
    while(counter < m_MaxPopulation && !m_Abort)
    {	
        Individual *indiv = &m_Individuals[counter];
        for(unsigned int i = 0;i<weo::engine::Slotcount;i++)
        {
            if(!m_Locked[i])
            {
                populateSlot(*indiv,i);
            }
            else
            {
                indiv->Items[i] = m_Equip.Items[i];
                for(int k = 0;k<3;k++)
                {
                    indiv->Gems[i*3+k] = m_Equip.Gems[i*3+k];
                }
                indiv->Enchants[i] = m_Equip.Enchants[i];
            }
        }
        indiv->Fitness = 0;
        indiv->Hash = 0;
        if(Valid(*indiv))
        {
            counter++;
        }
    }
    if(m_Abort) return;
    for(unsigned int i = 0;i<m_MaxPopulation;i++)
    {
        if(m_Individuals[i].Fitness == 0)
        {			
            evaluateTF(m_Individuals[i]);
        }
    }
    counter = 0;
    while(currentgen-bestgen < 50 && !m_Abort)
    {
        m_StartLoop.restart();
        for(unsigned int i = 0;i<childcount;i++)
        {
            if(m_Individuals[i].Fitness == 0)
            {
                evaluateTF(m_Individuals[i]);
            }
        }
        std::sort(m_Individuals,m_Individuals+m_MaxPopulation,cmpindiv);
        float curbest = m_Individuals[m_MaxPopulation-1].Fitness;
        if(curbest > besttv)
        {
            besttv = curbest;
            bestgen = currentgen;
        }
        currentgen++;
        counter = 0;
        m_StartLoop.restart();
        Tournamentselection();
        m_StartLoop.restart();
        childcount = 0;
        int mutatecount = 0;
        while (m_Individuals[childcount].Fitness == 0 && !m_Abort)
        {
            Individual* p1 = &m_Individuals[std::rand() % m_MaxPopulation];
            while(p1->Fitness == 0)
            {
                p1 = &m_Individuals[std::rand() % m_MaxPopulation];
            }
            Individual* p2 = &m_Individuals[std::rand() % m_MaxPopulation];
            while(p2->Fitness == 0)
            {
                p2 = &m_Individuals[std::rand() % m_MaxPopulation];
            }
            if(p1->Hash == p2->Hash) continue;
            Individual child;
            if(Crossover(child, *p1, *p2))
            {
                child.Generation = currentgen;
                m_Individuals[childcount] = child;
                childcount++;
            }
        }
        counter = 0;
        m_StartLoop.restart();
        while(counter < m_MaxPopulation / 8 && !m_Abort)
        {

            int nr = std::rand() % childcount;
            Individual mutant = m_Individuals[nr];
            if(Mutate(mutant))
            {
                counter++;
                m_Individuals[nr] = mutant;
            }			
        }
        m_UpdateSignal(besttv);
    }
    std::sort(m_Individuals,m_Individuals+m_MaxPopulation,cmpindiv);
    Individual *max = &m_Individuals[m_MaxPopulation-1];
    m_Equip.setEquip(max->Items,max->Gems,max->Enchants);
    m_Equip.Fitness = max->Fitness;
    OptimizeGems og;
    og.Character(m_Character);
    og.setItems(m_Items);
    og.setEquip(m_Equip);
    og.Optimize();
    m_Equip.setEquip(og.getEquip()->Items,og.getEquip()->Gems,og.getEquip()->Enchants);
    m_Equip.Fitness = og.getEquip()->Fitness;
    m_EndSignal();
}
void OptimizeFull::Output(const Individual& indiv)
{
    cout << "Items:" << endl;
    for(unsigned int i = 0;i<weo::engine::Slotcount;i++)
    {
        cout << "Item: " << indiv.Items[i]->Name() << endl;
        for(unsigned int j = 0;j<indiv.Items[i]->Sockets()->Count();j++)
        {
            cout << "\tGem: " << indiv.Gems[i*3+j]->Name() << endl;
        }
        if(indiv.Enchants[i] != NULL)
        {
            cout << "Enchant: " << indiv.Enchants[i]->Name() << endl;
        }
    }
    m_Character->Equip().UseSpecial(false);
    m_Character->evaluateEquip(indiv.Items,indiv.Gems,indiv.Enchants);
    float fitness = m_Character->evaluateTF();
    m_Character->Equip().UseSpecial(true);
    cout << m_Character->targetValue() << ": " << indiv.Fitness << endl;
    cout << "Attributes: " << endl;
    const vector<string>* attrs = m_Character->getAttributes();
    for(unsigned int i = m_Character->getAttributesStart();i<attrs->size();i++)
    {
        cout << "\t" << attrs->at(i) << ": " << m_Character->Equip().Get(weo::engine::item::Attribute::getAttributeValue(attrs->at(i))) << endl;
    }
    cout << "Runtime: " << m_Runtime.elapsed() << " seconds" << endl;
}
OptimizeFull::~OptimizeFull()
{
    m_HashSet.clear();
    m_HashSet.resize(0);
    if(m_Individuals) delete[] m_Individuals;
}